<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2.模拟光照让3D场景更逼真-Phong反射模型</title>
    <style>
        canvas{
            border:1px solid #aaa;
            margin: 0 auto;
            display: block;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>

    <script type="module">
        import { Renderer, Camera, Transform, Program, Color, Mesh, Texture, Orbit, Sphere, Box, Cylinder, Torus } from '/common/lib/ogl/index.mjs';
        import * as dat from '/common/lib/dat.gui.js';
        import { Phong, Material } from "/common/lib/phong.js"
        
        const phong = new Phong();
        // 添加一个平行光
        phong.addLight({
            direction: [-1, 0, 0],
        });
        // 添加两个点光源
        phong.addLight({
            position: [-3, 3, 0],
            color: [1, 0, 0],
        });

        phong.addLight({
            position: [3, 3, 0],
            color: [0, 0, 1],
        });

        // 分别对应4个几何体的材质
        const matrial1 = new Material(new Color('#0000ff'), 2.0);
        const matrial2 = new Material(new Color('#ff00ff'), 2.0);
        const matrial3 = new Material(new Color('#008000'), 2.0);
        const matrial4 = new Material(new Color('#ff0000'), 2.0);

        let canvas = document.querySelector("canvas");
        // 1. 创建画布宽高512的renderer对象
        const renderer = new Renderer({
            canvas,
            width:512,
            height:512,
        });

        const gl = renderer.gl;
        gl.clearColor(1, 1, 1, 1);

        (async function(){
            // 2. 创建相机（默认透视投影）
            // 视角35°
            const camera = new Camera(gl, { fov: 35 });
            // 相机的位置
            camera.position.set(0, 0, 7);
            // 相机的朝向
            camera.lookAt([0, 0, 0]);

            // 3. 创建场景
            const scene = new Transform();

            // 4. 创建几何体对象
            const sphereGeometry = new Sphere(gl);
            const cubeGeometry = new Box(gl);
            const cylinderGeometry = new Cylinder(gl);
            const torusGeometry = new Torus(gl);
                    
            // 5. 创建webgl程序
            const vertex = /* glsl */ `
                precision highp float;
                
                attribute vec3 position;
                attribute vec3 normal;
                
                uniform mat4 modelViewMatrix;
                uniform mat4 projectionMatrix;
                uniform mat3 normalMatrix;
                
                varying vec3 vNormal;
                varying vec4 vPos;
                
                void main() {
                    vPos = modelViewMatrix * vec4(position, 1.0);
                    // 计算法向量
                    vNormal = normalize(normalMatrix * normal);
                    gl_Position = projectionMatrix * vPos;
                }
            `;
            
            const fragment = /* glsl */ `
                #ifdef GL_ES
                precision mediump float;
                #endif
                #define MAX_LIGHT_COUNT 16

                uniform vec3 vCameraPos;
                uniform mat4 viewMatrix;

                // 声明 vec3 和 float 数组，数组的大小为 16。这样，对于每一种光源，我们都可以支持 16 个。
                uniform vec3 ambientLight;
                uniform vec3 directionalLightDirection[MAX_LIGHT_COUNT];
                uniform vec3 directionalLightColor[MAX_LIGHT_COUNT];
                uniform vec3 pointLightColor[MAX_LIGHT_COUNT];
                uniform vec3 pointLightPosition[MAX_LIGHT_COUNT];
                uniform vec3 pointLightDecay[MAX_LIGHT_COUNT];
                uniform vec3 spotLightColor[MAX_LIGHT_COUNT];
                uniform vec3 spotLightDirection[MAX_LIGHT_COUNT];
                uniform vec3 spotLightPosition[MAX_LIGHT_COUNT];
                uniform vec3 spotLightDecay[MAX_LIGHT_COUNT];
                uniform float spotLightAngle[MAX_LIGHT_COUNT];

                // 材质相关的属性
                uniform vec3 materialReflection;
                uniform float shininess;
                uniform float specularFactor;

                varying vec3 vNormal;
                varying vec4 vPos;

                // 镜面反射强度
                float getSpecular(vec3 dir, vec3 normal, vec3 eye){
                    // 1. 求出反射光线的方向向量
                    vec3 reflectionLight = reflect(-dir, normal);
                    // 2. 求反射光线与视线夹角的余弦
                    float eyeCos = max(dot(eye, reflectionLight), 0.0);
                    // 3.使用材质的镜面反射光洁度和材质的镜面反射强度设置镜面反射强度，指数越大，镜面越聚焦，高光的光斑范围就越小
                    return specularFactor *  pow(eyeCos, shininess);
                }

                // 获取反射模型的颜色
                vec4 phongReflection(vec3 pos, vec3 normal, vec3 eye) {
                    // 镜面反射结果
                    float specular = 0.0;  
                    // 漫反射结果
                    vec3 diffuse = vec3(0);

                    // 处理平行光
                    for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
                        // 取值三维坐标
                        vec3 dir = directionalLightDirection[i];
                        // 如果平行光是0向量，跳过处理
                        if(dir.x == 0.0 && dir.y == 0.0 && dir.z == 0.0) continue;
                        // 对平行光向量按照相机位置，进行视图变换
                        vec4 d = viewMatrix * vec4(dir, 0.0);
                        // 平行光归一化处理
                        dir = normalize(-d.xyz);
                        // 平行光与每点法向量的夹角余弦值
                        float cos = max(dot(dir, normal), 0.0);
                        // 平行光照射的强度合成
                        diffuse += cos * directionalLightColor[i];
                        // 平行光对几何体的镜面反射的效果
                        specular += getSpecular(dir, normal, eye);
                    }

                    // 处理点光源
                    for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
                        // 如果衰减系数都是0，跳过处理
                        vec3 decay = pointLightDecay[i];    
                        if(decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) continue;

                        // 取值三维坐标
                        vec3 dir = (viewMatrix * vec4(pointLightPosition[i], 1.0)).xyz - pos.xyz;
                        // 光线到点坐标的距离，用来计算衰减 
                        float dis = length(dir);
                        // 点光归一化处理
                        dir = normalize(dir);
                        // 点光与每点法向量的夹角余弦值
                        float cos = max(dot(dir, normal), 0.0);
                        // 计算衰减 
                        float d = min(1.0, 1.0 / (decay.x * pow(dis, 2.0) + decay.y * dis + decay.z));
                        // 点光照射的强度合成
                        diffuse += d * cos * pointLightColor[i];
                        // 点光对几何体的镜面反射的效果
                        specular += getSpecular(dir, normal, eye);
                    }

                    // 处理聚光灯
                    for(int i = 0; i < MAX_LIGHT_COUNT; i++) {
                        // 如果衰减系数都是0，跳过处理
                        vec3 decay = spotLightDecay[i];    
                        if(decay.x == 0.0 && decay.y == 0.0 && decay.z == 0.0) continue;

                        // 取值三维坐标
                        vec3 dir = (viewMatrix * vec4(spotLightPosition[i], 1.0)).xyz - pos.xyz;
                        // 光线到点坐标的距离，用来计算衰减 
                        float dis = length(dir);
                        // 聚光灯归一化处理
                        dir = normalize(dir);
                        // 聚光灯的朝向 
                        vec3 spotDir = (viewMatrix * vec4(spotLightDirection[i], 0.0)).xyz;
                        // 通过余弦值判断夹角范围 
                        float ang = cos(spotLightAngle[i]); 
                        // 计算聚光灯的朝向与点光源到物体表面的点坐标的方向的余弦值,比较两个夹角
                        float r = step(ang, dot(dir, normalize(-spotDir)));

                        // 聚光灯与每点法向量的夹角余弦值
                        float cos = max(dot(dir, normal), 0.0);
                        // 计算衰减 
                        float d = min(1.0, 1.0 / (decay.x * pow(dis, 2.0) + decay.y * dis + decay.z));
                        // 聚光灯照射的强度合成
                        diffuse += r * d * cos * spotLightColor[i];
                        // 聚光灯对几何体的镜面反射的效果
                        specular += r * getSpecular(dir, normal, eye);
                    }
                    return vec4(diffuse, specular);
                }

                void main() {
                    // 根据相机位置计算对每个点的视线的向量方向
                    vec3 eyeDirection = normalize(vCameraPos - vPos.xyz);  
                    vec4 phong = phongReflection(vPos.xyz, vNormal, eyeDirection);

                    // 合成颜色
                    gl_FragColor.rgb = phong.w + (phong.xyz + ambientLight) * materialReflection;;
                    gl_FragColor.a = 1.0;
                }
            `;

            // 传入光的相关属性
            const directional = {
                vCameraPos:{ value: [0, 0, 7] },  
                ...phong.uniforms,
            };

            const program1 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ...matrial1.uniforms,    
                },
            });

            const program2 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ...matrial2.uniforms,    
                },
            });
            const program3 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ...matrial3.uniforms,    
                },
            });
            const program4 = new Program(gl, {
                vertex,
                fragment,
                uniforms: {
                    ...directional,
                    ...matrial4.uniforms,    
                },
            });
            
            // 6. 创建网格对象
            const controls = new Orbit(camera);
            
            const torus = new Mesh(gl, {geometry: torusGeometry, program: program1});
            torus.position.set(0, 1.3, 0);
            torus.setParent(scene);

            const sphere = new Mesh(gl, {geometry: sphereGeometry, program: program2});
            sphere.position.set(1.3, 0, 0);
            sphere.setParent(scene);

            const cube = new Mesh(gl, {geometry: cubeGeometry, program: program3});
            cube.position.set(0, -1.3, 0);
            cube.setParent(scene);

            const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program: program4});
            cylinder.position.set(-1.3, 0, 0);
            cylinder.setParent(scene);
            
            // 7. 渲染
            requestAnimationFrame(update);
            function update() {
                requestAnimationFrame(update);
                controls.update();
                renderer.render({scene, camera});
            }

        })()
    </script>
</body>
</html>